package wsi.match;

import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.rygh.core.model.common.MapList;
import org.rygh.core.model.common.SetManager;
import org.rygh.core.util.UIo;
import org.rygh.core.xml.XmlPath;
import org.rygh.semantic.gui.swing.input.Input;
import org.rygh.semantic.model.ex.FactSet;
import org.rygh.semantic.model.rdf.Entity;
import org.rygh.semantic.model.rdf.Predicate;

import wsi.dataguide.tree.WSXmlLeaf;
import wsi.dataguide.tree.WSXmlParser;
import wsi.dataguide.tree.WSXmlTree;
import wsi.function.DirManager;
import wsi.function.Function.Phrase;
import wsi.subgraph.ExtractWorker;
import wsi.subgraph.tree.EntityTree;
import wsi.subgraph.tree.EntityTreeLoader;
import wsi.task.TaskRoutine;

public class SubtreeMatcher extends TaskRoutine {

	protected final Input[] inputs;
	protected final XmlFunctionalityController xmlFunctionalityCtrl = new XmlFunctionalityController(
			dir);

	private final Matcher matcher = new Matcher();

	protected final Map<Input, EntityTree> entityTreeMap = new HashMap<>();

	protected final Map<Input, SubtreeMatch> supnodeMap = new HashMap<>();
	protected final Map<WSXmlLeaf, SubtreeMatch> matchMap = new HashMap<>();

	protected final Set<WSXmlLeaf> unmatchedLeaves = new HashSet<>();
	protected final Set<WSXmlLeaf> unmatchedSupLeaves = new HashSet<>();

	protected final FunctionalityXPath functionalityXPath;
	protected final SubtreeController subtreeController;;
	protected final SubtreeCounter subtreeCounter;

	protected MapList<Input, WSXmlLeaf> subrootMapList;

	public SubtreeMatcher(DirManager dir) {
		super(dir);

		subtreeController = new SubtreeController(dir);
		functionalityXPath = new FunctionalityXPath(dir, subtreeController);
		inputs = dir.getInputs();

		subtreeCounter = new SubtreeCounter(inputs);
		subtreeCounter.setMatchMap(matchMap);

		matcher.setMatchedLeaves(new HashSet<WSXmlLeaf>());

		if (!xmlFunctionalityCtrl.load()) {
			log.info("Failed to load xml functionality.");
			assert false;
			return;
		}

	}

	@Override
	public void compute() {
		// log.info("computing");
		subrootMapList = subtreeController.computeIsomorphicRoot();
		matchSubtrees();
	}

	public void computeStatistics() {
		subtreeCounter.computeStatistics(supnodeMap,
				subtreeController.getSubrootCount());
	}

	private void match() {
		// System.out.println("matching");

		EntityTree entityTree;
		Entity entity;

		List<WSXmlLeaf> supleaves;
		SubtreeMatch supnodeMatch, subtreeMatch;
		WSXmlLeaf master_node;
		WSXmlParser parser;
		WSXmlTree xmlTree;
		FactSet group;
		String fname;

		Map<Input, File> fileMap = dir.getFileMap();
		ExtractWorker yagohop = new ExtractWorker(dir, null);

		/*
		 * for common master, it's fine to get the parent here. artist ->
		 * artist-list.
		 * 
		 * for special master, the root, we do not go one level upper.
		 */

		WSXmlLeaf common_root = subtreeController.getCommonRoot();
		// System.out.println("common root " + common_root.getXpath());

		WSXmlLeaf common_parent = common_root.getParent();
		// System.out.println("common_parent " + common_parent.getXpath());

		if (common_parent.isRoot())
			common_parent = common_root;

		XmlPath common_path = common_parent.getXpath();

		List<XmlPath> idPredicateXmlPaths = null;
		Set<Predicate> id_preds = function.getIdPredicates();

		if (id_preds != null) {
			idPredicateXmlPaths = new LinkedList<>();
			XmlPath root_xpath = common_root.getXpath();

			for (Predicate p : id_preds) {
				XmlPath xp = root_xpath.appendRelativePath(p.toXmlPathString());
				idPredicateXmlPaths.add(xp);
			}
		}

		System.out.println("idPredicateXmlPaths " + idPredicateXmlPaths);
		matcher.setIdPredicateXmlPaths(idPredicateXmlPaths);

		for (Input input : inputs) {
			// log.info("Subtree Matcher: input " + input);

			if (!fileMap.containsKey(input)) {
				System.out.printf("[warn] no xml for %s\n", input);
				// assert false;
				continue;
			}

			fname = input.toFilename();

			group = yagohop.load(fname);
			if (group == null || group.isEmpty()) {
				System.out.printf("[error] no rdf subgraph for %s\n", input);
				// assert false : fname;
				continue;
			}

			entity = input.getEntity();

			if (entity == null) {
				System.out.printf("[error] no entity for %s\n", input);
				continue;
			}

			if (entityTreeMap.containsKey(input))
				entityTree = entityTreeMap.get(input);
			else {
				EntityTreeLoader loader = new EntityTreeLoader(entity, group);
				entityTree = loader.load();

				entityTreeMap.put(input, entityTree);
			}

			// match for common suptree. (super tree, the part above master)

			String xml = UIo.readFile(fileMap.get(input));

			if (xml.isEmpty()) {
				System.out.printf("[warn] empty xml for %s\n", input);
				continue;
			}

			parser = new WSXmlParser(input, xml, true);
			xmlTree = parser.parse();
			master_node = xmlTree.getNodeByXPath(common_path);

			if (master_node == null) {
				System.out.printf("[warn] no master node at %s\n", common_path);

				master_node = xmlTree.getLeastNodeByXPath(common_path);
				assert master_node != null : common_path;
			}

			supleaves = xmlTree.getLeavesAbove(master_node);
			subtreeController.putSupLeaves(input, supleaves);

			supnodeMatch = matcher.match(input, entityTree, null, supleaves);

			if (supnodeMatch != null) {
				if (supnodeMap.containsKey(input)) {
					log.info(String
							.format("[warn] duplicated input %s, override the previous.",
									input));
				}

				supnodeMap.put(input, supnodeMatch);
			}

			{
				// SubtreeMatch sm = supnodeMap.get(input);
				// sm.setXmlFunctionalityController(xmlFunctionalityCtrl);
				// sm.compute();
			}

			// match for each isomorphic subtree.

			List<WSXmlLeaf> subroots = subrootMapList.get(input);
			if (subroots == null) {
				System.out
						.printf("[warn.subtree.matcher.match] no subtrees found for %s\n",
								input);
				continue;
			}

			for (WSXmlLeaf subroot : subroots) {
				List<WSXmlLeaf> leaves = subroot.getAllLeaves();
				subtreeController.putLeaves(subroot, leaves);

				subtreeMatch = matcher
						.match(input, entityTree, subroot, leaves);

				/*
				 * inject (grafting) matches for sup-nodes into matches for a
				 * subtree. sup-nodes matches contain common information.
				 */

				if (subtreeMatch != null) {
					if (supnodeMatch != null)
						subtreeMatch.graftPairs(supnodeMatch);

					if (matchMap.containsKey(subroot)) {
						System.out.printf(
								"[warn] duplicated subroot %s, override.\n",
								subroot.getXpath());
					}

					matchMap.put(subroot, subtreeMatch);

					{// to print
						// System.out.printf("subtree @ %s has %d matches.\n",
						// subroot
						// .getXpath().getValue(), matchMap
						// .containsKey(subroot) ? matchMap.get(subroot)
						// .getMatchPairs().size() : 0);
						//
						// if (matchMap.containsKey(subroot)) {
						// for (MatchPair k : matchMap.get(subroot)
						// .getMatchPairs())
						// System.out.printf("\t%s %s\n", k.getLeaf()
						// .getXpath(), k.getNode().getValue()
						// .getValue().getValue());
						// }
					}
				}

				{
					// SubtreeMatch sm = matchMap.get(subroot);
					// sm.setXmlFunctionalityController(xmlFunctionalityCtrl);
					// sm.compute();
				}
			}

			if (function.isSkipped(Phrase.REST)) {
				log.info("break for trial.");
				break;
			}
		}
	}

	protected void matchSubtrees() {
		matchMap.clear();
		supnodeMap.clear();
		unmatchedLeaves.clear();
		unmatchedSupLeaves.clear();

		matcher.clear();
		// the matchedLeaves set contains all matched leaves of all subtrees

		match();
		processUnmatched();
	}

	private void processUnmatched() {
		Set<WSXmlLeaf> matchedLeaves = matcher.getMatchedLeaves();
		int matched_id, unmatched_id;

		matched_id = SetManager.shared().begin();
		unmatched_id = SetManager.shared().begin();

		List<WSXmlLeaf> subroots;
		List<WSXmlLeaf> supleaves, leaves;

		for (Input input : inputs) {
			// log.info("processing unmatched for input " + input);

			// match for common suptree. (super tree, the part above master)

			supleaves = subtreeController.getSupLeaves(input);

			if (supleaves != null) {
				processUnmatched(matchedLeaves, supleaves, true, matched_id,
						unmatched_id);
			}

			// match for each isomorphic subtree.

			subroots = subrootMapList.get(input);
			if (subroots == null) {
				// log.info("no subtrees found.");
				continue;
			}

			for (WSXmlLeaf subroot : subroots) {
				// log.info(String.format("process unmatched for subroot %s",
				// subroot.getXpath().getValue()));

				leaves = subtreeController.getLeaves(subroot);

				if (leaves != null) {
					processUnmatched(matchedLeaves, leaves, false, matched_id,
							unmatched_id);
				}
			}

			if (function.isSkipped(Phrase.REST)) {
				log.info("break for trial.");
				break;
			}
		}

		SetManager.shared().end(matched_id);
		SetManager.shared().end(unmatched_id);

		subtreeCounter.getUnmatchedSupCounter().setNormalizeFactor(
				supnodeMap.size());
		subtreeCounter.getUnmatchedCounter().setNormalizeFactor(
				subtreeController.getSubrootCount());
	}

	private void processUnmatched(Set<WSXmlLeaf> matchedLeaves,
			List<WSXmlLeaf> leaves, boolean isSuper, int matched_id,
			int unmatched_id) {
		assert leaves != null;

		// dealing with unmatched leaves.

		// count tag-list/tag/@count only once if there are multiple
		Set<WSXmlLeaf> set = new HashSet<>();
		Set<WSXmlLeaf> setSup = new HashSet<>();

		for (WSXmlLeaf leaf : leaves) {
			if (matchedLeaves.contains(leaf))
				continue;

			// dealing with the case where other siblings are matched. such as
			// alias.

			boolean matched = false;
			String xpath = leaf.getXpath().stripIndex();

			if (SetManager.shared().contains(matched_id, xpath))
				continue;

			if (!SetManager.shared().contains(unmatched_id, xpath)) {

				for (SubtreeMatch sm : matchMap.values()) {
					// this loop is time-consuming.

					for (MatchPair mp : sm.getMatchPairs()) {
						WSXmlLeaf l = mp.getXmlLeaf();

						if (xpath.equals(l.getXpath().stripIndex())) {
							matched = true;
							break;
						}
					}

					if (matched)
						break;
				}
			}

			if (matched) {
				SetManager.shared().add(matched_id, xpath);
				continue;
			}

			WSXmlLeaf dg_leaf = dataGuide.getIsomorphicLeaf(leaf);
			{// we need to count whatsoever.

				if (dg_leaf == null)
					continue;

				if (isSuper) {
					if (!setSup.contains(dg_leaf)) {
						setSup.add(dg_leaf);
						subtreeCounter.getUnmatchedSupCounter().count(dg_leaf);
					}
				} else {
					if (!set.contains(dg_leaf)) {
						set.add(dg_leaf);
						subtreeCounter.getUnmatchedCounter().count(dg_leaf);
					}
				}
			}

			if (!SetManager.shared().contains(unmatched_id, xpath)) {
				if (isSuper) {
					unmatchedSupLeaves.add(dg_leaf);
				} else {
					unmatchedLeaves.add(dg_leaf);
				}

				SetManager.shared().add(unmatched_id, xpath);
			}
		}
	}

	@Override
	public String save() {
		String res = toString();
		UIo.saveFile(
				new File(dir.getDebugDir(), "_debug_subtree_mapping.html"), res);

		return res;
	}

	@Override
	public String toString() {
		String title = dir.getServiceName() + "/" + function.getName();

		return subtreeCounter.toString(title, subrootMapList);
	}
}
